﻿HmJS.$Import('core.core');

HmJS.register('core.more', function ($ns) {

	// #region -- More --

	/*
	---

	script: More.js

	name: More

	description: MooTools More

	license: MIT-style license

	authors:
	- Guillermo Rauch
	- Thomas Aylott
	- Scott Kyle
	- Arian Stolwijk
	- Tim Wienk
	- Christoph Pojer
	- Aaron Newton
	- Jacob Thornton

	requires:
	- Core/MooTools

	provides: [MooTools.More]

	...
	*/

	MooTools.More = {
		'version': '1.4.0.1',
		'build': 'a4244edf2aa97ac8a196fc96082dd35af1abab87'
	};

	// #endregion

	// #region -- Locale.en-US.Date --

	/**
	* name: Locale.en-US.Date
	* 
	* description: Date messages for US English.
	* 
	* authors:
	*		- Aaron Newton
	* 
	* requires:
	*		- /Locale
	* 
	* provides: [Locale.en-US.Date]
	**/

	Locale.define('en-US', 'Date', {

		months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
		months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
		days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
		days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],

		// Culture's date order: MM/DD/YYYY
		dateOrder: ['month', 'date', 'year'],
		shortDate: '%m/%d/%Y',
		shortTime: '%I:%M%p',
		AM: 'AM',
		PM: 'PM',
		firstDayOfWeek: 0,

		// Date.Extras
		ordinal: function (dayOfMonth) {
			// 1st, 2nd, 3rd, etc.
			return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
		},

		lessThanMinuteAgo: 'less than a minute ago',
		minuteAgo: 'about a minute ago',
		minutesAgo: '{delta} minutes ago',
		hourAgo: 'about an hour ago',
		hoursAgo: 'about {delta} hours ago',
		dayAgo: '1 day ago',
		daysAgo: '{delta} days ago',
		weekAgo: '1 week ago',
		weeksAgo: '{delta} weeks ago',
		monthAgo: '1 month ago',
		monthsAgo: '{delta} months ago',
		yearAgo: '1 year ago',
		yearsAgo: '{delta} years ago',

		lessThanMinuteUntil: 'less than a minute from now',
		minuteUntil: 'about a minute from now',
		minutesUntil: '{delta} minutes from now',
		hourUntil: 'about an hour from now',
		hoursUntil: 'about {delta} hours from now',
		dayUntil: '1 day from now',
		daysUntil: '{delta} days from now',
		weekUntil: '1 week from now',
		weeksUntil: '{delta} weeks from now',
		monthUntil: '1 month from now',
		monthsUntil: '{delta} months from now',
		yearUntil: '1 year from now',
		yearsUntil: '{delta} years from now'

	});

	// #endregion Locale.en-US.Date

	// #region -- Locale.zh-CH.Date --

	/**
	* name: Locale.zh-CH.Date
	* 
	* description: Date messages for Chinese (simplified and traditional).
	* 
	* authors:
	*		- YMind Chan
	* 
	* requires:
	*		- /Locale
	* 
	* provides: [Locale.zh-CH.Date]
	*/

	// Simplified Chinese
	Locale.define('zh-CHS', 'Date', {

		months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
		months_abbr: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'],
		days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
		days_abbr: ['日', '一', '二', '三', '四', '五', '六'],

		// Culture's date order: YYYY-MM-DD
		dateOrder: ['year', 'month', 'date'],
		shortDate: '%Y-%m-%d',
		shortTime: '%I:%M%p',
		AM: 'AM',
		PM: 'PM',
		firstDayOfWeek: 1,

		// Date.Extras
		ordinal: '',

		lessThanMinuteAgo: '不到1分钟前',
		minuteAgo: '大约1分钟前',
		minutesAgo: '{delta}分钟之前',
		hourAgo: '大约1小时前',
		hoursAgo: '大约{delta}小时前',
		dayAgo: '1天前',
		daysAgo: '{delta}天前',
		weekAgo: '1星期前',
		weeksAgo: '{delta}星期前',
		monthAgo: '1个月前',
		monthsAgo: '{delta}个月前',
		yearAgo: '1年前',
		yearsAgo: '{delta}年前',

		lessThanMinuteUntil: '从现在开始不到1分钟',
		minuteUntil: '从现在开始約1分钟',
		minutesUntil: '从现在开始约{delta}分钟',
		hourUntil: '从现在开始1小时',
		hoursUntil: '从现在开始约{delta}小时',
		dayUntil: '从现在开始1天',
		daysUntil: '从现在开始{delta}天',
		weekUntil: '从现在开始1星期',
		weeksUntil: '从现在开始{delta}星期',
		monthUntil: '从现在开始一个月',
		monthsUntil: '从现在开始{delta}个月',
		yearUntil: '从现在开始1年',
		yearsUntil: '从现在开始{delta}年'

	});

	// Traditional Chinese
	Locale.define('zh-CHT', 'Date', {

		months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
		months_abbr: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'],
		days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
		days_abbr: ['日', '一', '二', '三', '四', '五', '六'],

		// Culture's date order: YYYY-MM-DD
		dateOrder: ['year', 'month', 'date'],
		shortDate: '%Y-%m-%d',
		shortTime: '%I:%M%p',
		AM: 'AM',
		PM: 'PM',
		firstDayOfWeek: 1,

		// Date.Extras
		ordinal: '',

		lessThanMinuteAgo: '不到1分鐘前',
		minuteAgo: '大約1分鐘前',
		minutesAgo: '{delta}分鐘之前',
		hourAgo: '大約1小時前',
		hoursAgo: '大約{delta}小時前',
		dayAgo: '1天前',
		daysAgo: '{delta}天前',
		weekAgo: '1星期前',
		weeksAgo: '{delta}星期前',
		monthAgo: '1个月前',
		monthsAgo: '{delta}个月前',
		yearAgo: '1年前',
		yearsAgo: '{delta}年前',

		lessThanMinuteUntil: '從現在開始不到1分鐘',
		minuteUntil: '從現在開始約1分鐘',
		minutesUntil: '從現在開始約{delta}分鐘',
		hourUntil: '從現在開始1小時',
		hoursUntil: '從現在開始約{delta}小時',
		dayUntil: '從現在開始1天',
		daysUntil: '從現在開始{delta}天',
		weekUntil: '從現在開始1星期',
		weeksUntil: '從現在開始{delta}星期',
		monthUntil: '從現在開始一個月',
		monthsUntil: '從現在開始{delta}個月',
		yearUntil: '從現在開始1年',
		yearsUntil: '從現在開始{delta}年'

	});

	// #endregion Locale.zh-CH.Date

	// #region -- Date --

	/**
	* name: Date
	* 
	* description: Extends the Date native object to include methods useful in managing dates.
	* 
	* authors:
	*		- Aaron Newton
	*		- Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
	*		- Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
	*		- Scott Kyle - scott [at] appden.com; http://appden.com
	* 
	* requires:
	*		- Core/Array
	*		- Core/String
	*		- Core/Number
	*		- MooTools.More
	*		- Locale
	*		- Locale.en-US.Date
	* 
	* provides: [Date]
	**/

	(function () {

		var Date = this.Date;

		var DateMethods = Date.Methods = {
			ms: 'Milliseconds',
			year: 'FullYear',
			min: 'Minutes',
			mo: 'Month',
			sec: 'Seconds',
			hr: 'Hours'
		};

		['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function (method) {
		Date.Methods[method.toLowerCase()] = method;
	});

		var pad = function (n, digits, string) {
			if (digits == 1) { return n; }
			return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
		};

		Date.implement({

			set: function (prop, value) {
				prop = prop.toLowerCase();
				var method = DateMethods[prop] && 'set' + DateMethods[prop];
				if (method && this[method]) { this[method](value); }
				return this;
			} .overloadSetter(),

			get: function (prop) {
				prop = prop.toLowerCase();
				var method = DateMethods[prop] && 'get' + DateMethods[prop];
				if (method && this[method]) { return this[method](); }
				return null;
			} .overloadGetter(),

			clone: function () {
				return new Date(this.get('time'));
			},

			increment: function (interval, times) {
				interval = interval || 'day';
				times = times != null ? times : 1;

				switch (interval) {
					case 'year':
						return this.increment('month', times * 12);

					case 'month':
						var d = this.get('date');
						this.set('date', 1).set('mo', this.get('mo') + times);
						return this.set('date', d.min(this.get('lastdayofmonth')));

					case 'week':
						return this.increment('day', times * 7);

					case 'day':
						return this.set('date', this.get('date') + times);
				}

				if (!Date.units[interval]) {
					throw new Error(interval + ' is not a supported interval');
				}

				return this.set('time', this.get('time') + times * Date.units[interval]());
			},

			decrement: function (interval, times) {
				return this.increment(interval, -1 * (times != null ? times : 1));
			},

			isLeapYear: function () {
				return Date.isLeapYear(this.get('year'));
			},

			clearTime: function () {
				return this.set({ hr: 0, min: 0, sec: 0, ms: 0 });
			},

			diff: function (date, resolution) {
				if (typeOf(date) == 'string') {
					date = Date.parse(date);
				}

				return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
			},

			getLastDayOfMonth: function () {
				return Date.daysInMonth(this.get('mo'), this.get('year'));
			},

			getDayOfYear: function () {
				return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
			},

			setDay: function (day, firstDayOfWeek) {
				if (firstDayOfWeek == null) {
					firstDayOfWeek = Date.getMsg('firstDayOfWeek');
					if (firstDayOfWeek === '') { firstDayOfWeek = 1; }
				}

				day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
				var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;

				return this.increment('day', day - currentDay);
			},

			getWeek: function (firstDayOfWeek) {
				if (firstDayOfWeek == null) {
					firstDayOfWeek = Date.getMsg('firstDayOfWeek');
					if (firstDayOfWeek === '') { firstDayOfWeek = 1; }
				}

				var date = this,
						dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
						dividend = 0,
						firstDayOfYear;

				if (firstDayOfWeek == 1) {
					// ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
					var month = date.get('month'),
							startOfWeek = date.get('date') - dayOfWeek;

					if (month == 11 && startOfWeek > 28) { return 1; } // Week 1 of next year

					if (month == 0 && startOfWeek < -2) {
						// Use a date from last year to determine the week
						date = new Date(date).decrement('day', dayOfWeek);
						dayOfWeek = 0;
					}

					firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
					if (firstDayOfYear > 4) { dividend = -7; } // First week of the year is not week 1
				} else {
					// In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
					// Days in the same week can have a different weeknumber if the week spreads across two years.
					firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
				}

				dividend += date.get('dayofyear');
				dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
				dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week

				return (dividend / 7);
			},

			getOrdinal: function (day) {
				return Date.getMsg('ordinal', day || this.get('date'));
			},

			getTimezone: function () {
				return this.toString()
						.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
						.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
			},

			getGMTOffset: function () {
				var off = this.get('timezoneOffset');
				return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
			},

			setAMPM: function (ampm) {
				ampm = ampm.toUpperCase();
				var hr = this.get('hr');
				if (hr > 11 && ampm == 'AM') {
					return this.decrement('hour', 12);
				} else if (hr < 12 && ampm == 'PM') {
					return this.increment('hour', 12);
				}
				return this;
			},

			getAMPM: function () {
				return (this.get('hr') < 12) ? 'AM' : 'PM';
			},

			parse: function (str) {
				this.set('time', Date.parse(str));
				return this;
			},

			isValid: function (date) {
				if (!date) { date = this; }
				return typeOf(date) == 'date' && !isNaN(date.valueOf());
			},

			format: function (format) {
				if (!this.isValid()) {
					return 'invalid date';
				}

				if (!format) {
					format = '%x %X';
				}
				if (typeof format == 'string') {
					format = formats[format.toLowerCase()] || format;
				}
				if (typeof format == 'function') {
					return format(this);
				}

				var d = this;
				return format.replace(/%([a-z%])/gi, function ($0, $1) {
					switch ($1) {
						case 'a': return Date.getMsg('days_abbr')[d.get('day')];

						case 'A': return Date.getMsg('days')[d.get('day')];

						case 'b': return Date.getMsg('months_abbr')[d.get('month')];

						case 'B': return Date.getMsg('months')[d.get('month')];

						case 'c': return d.format('%a %b %d %H:%M:%S %Y');

						case 'd': return pad(d.get('date'), 2);

						case 'e': return pad(d.get('date'), 2, ' ');

						case 'H': return pad(d.get('hr'), 2);

						case 'I': return pad((d.get('hr') % 12) || 12, 2);

						case 'j': return pad(d.get('dayofyear'), 3);

						case 'k': return pad(d.get('hr'), 2, ' ');

						case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');

						case 'L': return pad(d.get('ms'), 3);

						case 'm': return pad((d.get('mo') + 1), 2);

						case 'M': return pad(d.get('min'), 2);

						case 'o': return d.get('ordinal');

						case 'p': return Date.getMsg(d.get('ampm'));

						case 's': return Math.round(d / 1000);

						case 'S': return pad(d.get('seconds'), 2);

						case 'T': return d.format('%H:%M:%S');

						case 'U': return pad(d.get('week'), 2);

						case 'w': return d.get('day');

						case 'x': return d.format(Date.getMsg('shortDate'));

						case 'X': return d.format(Date.getMsg('shortTime'));

						case 'y': return d.get('year').toString().substr(2);

						case 'Y': return d.get('year');

						case 'z': return d.get('GMTOffset');

						case 'Z': return d.get('Timezone');
					}
					return $1;
				});
			},

			toISOString: function () {
				return this.format('iso8601');
			}

		}).alias({
			toJSON: 'toISOString',
			compare: 'diff',
			strftime: 'format'
		});

		// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
		var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
				rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

		var formats = {
			db: '%Y-%m-%d %H:%M:%S',
			compact: '%Y%m%dT%H%M%S',
			'short': '%d %b %H:%M',
			'long': '%B %d, %Y %H:%M',
			rfc822: function (date) {
				return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
			},
			rfc2822: function (date) {
				return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
			},
			iso8601: function (date) {
				return (
			date.getUTCFullYear() + '-' +
			pad(date.getUTCMonth() + 1, 2) + '-' +
			pad(date.getUTCDate(), 2) + 'T' +
			pad(date.getUTCHours(), 2) + ':' +
			pad(date.getUTCMinutes(), 2) + ':' +
			pad(date.getUTCSeconds(), 2) + '.' +
			pad(date.getUTCMilliseconds(), 3) + 'Z'
		);
			}
		};

		var parsePatterns = [],
				nativeParse = Date.parse;

		var parseWord = function (type, word, num) {
			var ret = -1,
					translated = Date.getMsg(type + 's');
			switch (typeOf(word)) {
				case 'object':
					ret = translated[word.get(type)];
					break;

				case 'number':
					ret = translated[word];
					if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
					break;

				case 'string':
					var match = translated.filter(function (name) {
						return this.test(name);
					}, new RegExp('^' + word, 'i'));
					if (!match.length) {
						throw new Error('Invalid ' + type + ' string');
					}
					if (match.length > 1) {
						throw new Error('Ambiguous ' + type);
					}
					ret = match[0];
			}

			return (num) ? translated.indexOf(ret) : ret;
		};

		var startCentury = 1900,
				startYear = 70;

		Date.extend({

			getMsg: function (key, args) {
				return Locale.get('Date.' + key, args);
			},

			units: {
				ms: Function.from(1),
				second: Function.from(1000),
				minute: Function.from(60000),
				hour: Function.from(3600000),
				day: Function.from(86400000),
				week: Function.from(608400000),
				month: function (month, year) {
					var d = new Date;
					return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
				},
				year: function (year) {
					year = year || new Date().get('year');
					return Date.isLeapYear(year) ? 31622400000 : 31536000000;
				}
			},

			daysInMonth: function (month, year) {
				return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
			},

			isLeapYear: function (year) {
				return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
			},

			parse: function (from) {
				var t = typeOf(from);
				if (t == 'number') { return new Date(from); }
				if (t != 'string') { return from; }
				from = from.clean();
				if (!from.length) { return null; }

				var parsed;
				parsePatterns.some(function (pattern) {
					var bits = pattern.re.exec(from);
					return (bits) ? (parsed = pattern.handler(bits)) : false;
				});

				if (!(parsed && parsed.isValid())) {
					parsed = new Date(nativeParse(from));
					if (!(parsed && parsed.isValid())) {
						parsed = new Date(from.toInt());
					}
				}
				return parsed;
			},

			parseDay: function (day, num) {
				return parseWord('day', day, num);
			},

			parseMonth: function (month, num) {
				return parseWord('month', month, num);
			},

			parseUTC: function (value) {
				var localDate = new Date(value);
				var utcSeconds = Date.UTC(localDate.get('year'),
																	localDate.get('mo'),
																	localDate.get('date'),
																	localDate.get('hr'),
																	localDate.get('min'),
																	localDate.get('sec'),
																	localDate.get('ms'));
				return new Date(utcSeconds);
			},

			orderIndex: function (unit) {
				return Date.getMsg('dateOrder').indexOf(unit) + 1;
			},

			defineFormat: function (name, format) {
				formats[name] = format;
				return this;
			},


			defineParser: function (pattern) {
				parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
				return this;
			},

			defineParsers: function () {
				Array.flatten(arguments).each(Date.defineParser);
				return this;
			},

			define2DigitYearStart: function (year) {
				startYear = year % 100;
				startCentury = year - startYear;
				return this;
			}

		}).extend({
			defineFormats: Date.defineFormat.overloadSetter()
		});

		var regexOf = function (type) {
			return new RegExp('(?:' + Date.getMsg(type).map(function (name) {
				return name.substr(0, 3);
			}).join('|') + ')[a-z]*');
		};

		var replacers = function (key) {
			switch (key) {
				case 'T':
					return '%H:%M:%S';

				case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
					return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';

				case 'X':
					return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
			}
			return null;
		};

		var keys = {
			d: /[0-2]?[0-9]|3[01]/,
			H: /[01]?[0-9]|2[0-3]/,
			I: /0?[1-9]|1[0-2]/,
			M: /[0-5]?\d/,
			s: /\d+/,
			o: /[a-z]*/,
			p: /[ap]\.?m\.?/,
			y: /\d{2}|\d{4}/,
			Y: /\d{4}/,
			z: /Z|[+-]\d{2}(?::?\d{2})?/
		};

		keys.m = keys.I;
		keys.S = keys.M;

		var currentLanguage;

		var recompile = function (language) {
			currentLanguage = language;

			keys.a = keys.A = regexOf('days');
			keys.b = keys.B = regexOf('months');

			parsePatterns.each(function (pattern, i) {
				if (pattern.format) parsePatterns[i] = build(pattern.format);
			});
		};

		var build = function (format) {
			if (!currentLanguage) { return { format: format }; }

			var parsed = [];
			var re = (format.source || format) // allow format to be regex
					.replace(/%([a-z])/gi, function ($0, $1) {
						return replacers($1) || $0;
					})
					.replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
					.replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
					.replace(/%([a-z%])/gi, function ($0, $1) {
						var p = keys[$1];
						if (!p) { return $1; }
						parsed.push($1);
						return '(' + p.source + ')';
					})
					.replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words

			return {
				format: format,
				re: new RegExp('^' + re + '$', 'i'),
				handler: function (bits) {
					bits = bits.slice(1).associate(parsed);
					var date = new Date().clearTime(),
							year = bits.y || bits.Y;

					if (year != null) { handle.call(date, 'y', year); } // need to start in the right year
					if ('d' in bits) { handle.call(date, 'd', 1); }
					if ('m' in bits || bits.b || bits.B) { handle.call(date, 'm', 1); }

					for (var key in bits) {
						handle.call(date, key, bits[key]);
					}
					return date;
				}
			};
		};

		var handle = function (key, value) {
			if (!value) { return this; }

			switch (key) {
				case 'a': case 'A':
					return this.set('day', Date.parseDay(value, true));

				case 'b': case 'B':
					return this.set('mo', Date.parseMonth(value, true));

				case 'd':
					return this.set('date', value);

				case 'H': case 'I':
					return this.set('hr', value);

				case 'm':
					return this.set('mo', value - 1);

				case 'M':
					return this.set('min', value);

				case 'p':
					return this.set('ampm', value.replace(/\./g, ''));

				case 'S':
					return this.set('sec', value);

				case 's':
					return this.set('ms', ('0.' + value) * 1000);

				case 'w':
					return this.set('day', value);

				case 'Y':
					return this.set('year', value);

				case 'y':
					value = +value;
					if (value < 100) {
						value += startCentury + (value < startYear ? 100 : 0);
					}
					return this.set('year', value);
				case 'z':
					if (value == 'Z') { value = '+00'; }
					var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
					offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
					return this.set('time', this - offset * 60000);
			}

			return this;
		};

		Date.defineParsers(
	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
	'%Y %b( %d%o( %X)?)?', // Same as above with year coming first
	'%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
	'%T', // %H:%M:%S
	'%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
);

		Locale.addEvent('change', function (language) {
			if (Locale.get('Date')) { recompile(language); }
		}).fireEvent('change', Locale.getCurrent());

	})();

	// #endregion Date

	// #region -- Date.Extras --

	/**
	* name: Date.Extras
	* 
	* description: Extends the Date native object to include extra methods (on top of those in Date.js).
	* 
	* authors:
	*		- Aaron Newton
	*		- Scott Kyle
	* 
	* requires:
	*		- /Date
	* 
	* provides: [Date.Extras]
	**/

	Date.implement({

		timeDiffInWords: function (to) {
			return Date.distanceOfTimeInWords(this, to || new Date);
		},

		timeDiff: function (to, separator) {
			if (to == null) { to = new Date; }
			var delta = ((to - this) / 1000).floor().abs();

			var vals = [],
					durations = [60, 60, 24, 365, 0],
					names = ['s', 'm', 'h', 'd', 'y'],
					value, duration;

			/**
			# 苦苦的苦瓜
			# 2011-10-08
			# for (var item = 0; item < durations.length; item++) {
			**/
			for (var item = 0, len = durations.length; item < len; item++) {
				if (item && !delta) { break; }
				value = delta;
				if ((duration = durations[item])) {
					value = (delta % duration);
					delta = (delta / duration).floor();
				}
				vals.unshift(value + (names[item] || ''));
			}

			return vals.join(separator || ':');
		}

	}).extend({

		distanceOfTimeInWords: function (from, to) {
			return Date.getTimePhrase(((to - from) / 1000).toInt());
		},

		getTimePhrase: function (delta) {
			var suffix = (delta < 0) ? 'Until' : 'Ago';
			if (delta < 0) { delta *= -1; }

			var units = {
				minute: 60,
				hour: 60,
				day: 24,
				week: 7,
				month: 52 / 12,
				year: 12,
				eon: Infinity
			};

			var msg = 'lessThanMinute';

			for (var unit in units) {
				var interval = units[unit];
				if (delta < 1.5 * interval) {
					if (delta > 0.75 * interval) { msg = unit; }
					break;
				}
				delta /= interval;
				msg = unit + 's';
			}

			delta = delta.round();
			return Date.getMsg(msg + suffix, delta).substitute({ delta: delta });
		}

	}).defineParsers(

	{
		// "today", "tomorrow", "yesterday"
		re: /^(?:tod|tom|yes)/i,
		handler: function (bits) {
			var d = new Date().clearTime();
			switch (bits[0]) {
				case 'tom':
					return d.increment();

				case 'yes':
					return d.decrement();

				default:
					return d;
			}
		}
	},

	{
		// "next Wednesday", "last Thursday"
		re: /^(next|last) ([a-z]+)$/i,
		handler: function (bits) {
			var d = new Date().clearTime();
			var day = d.getDay();
			var newDay = Date.parseDay(bits[2], true);
			var addDays = newDay - day;
			if (newDay <= day) { addDays += 7; }
			if (bits[1] == 'last') { addDays -= 7; }
			return d.set('date', d.getDate() + addDays);
		}
	}

).alias('timeAgoInWords', 'timeDiffInWords');

	// #endregion Date.Extras

	// #region -- Locale.en-US.Number --

	/**
	* name: Locale.en-US.Number
	* 
	* description: Number messages for US English.
	* 
	* authors:
	*		- Arian Stolwijk
	* 
	* requires:
	*		- /Locale
	* 
	* provides: [Locale.en-US.Number]
	**/

	Locale.define('en-US', 'Number', {

		decimal: '.',
		group: ',',

		/* 	Commented properties are the defaults for Number.format
		decimals: 0,
		precision: 0,
		scientific: null,

		prefix: null,
		suffic: null,

		// Negative/Currency/percentage will mixin Number
		negative: {
		prefix: '-'
		},*/

		currency: {
			//		decimals: 2,
			prefix: '$ '
		}/*,

	percentage: {
		decimals: 2,
		suffix: '%'
	}*/

	});

	// #endregion Locale.en-US.Number

	// #region -- Number.Extras --

	/**
	* name: Number.Format
	* description: Extends the Number Type object to include a number formatting method.
	* authors: [Arian Stolwijk]
	* requires: [Core/Number, Locale.en-US.Number]
	* # Number.Extras is for compatibility
	* provides: [Number.Format, Number.Extras]
	**/

	Number.implement({

		format: function (options) {
			// Thanks dojo and YUI for some inspiration
			var value = this;
			options = options ? Object.clone(options) : {};
			var getOption = function (key) {
				if (options[key] != null) {
					return options[key];
				}
				return Locale.get('Number.' + key);
			};

			var negative = value < 0,
					decimal = getOption('decimal'),
					precision = getOption('precision'),
					group = getOption('group'),
					decimals = getOption('decimals');

			if (negative) {
				var negativeLocale = getOption('negative') || {};
				if (negativeLocale.prefix == null && negativeLocale.suffix == null) {
					negativeLocale.prefix = '-';
				}
				['prefix', 'suffix'].each(function (key) {
					if (negativeLocale[key]) {
						options[key] = getOption(key) + negativeLocale[key];
					}
				});

				value = -value;
			}

			var prefix = getOption('prefix'),
					suffix = getOption('suffix');

			if (decimals !== '' && decimals >= 0 && decimals <= 20) {
				value = value.toFixed(decimals);
			}
			if (precision >= 1 && precision <= 21) {
				value = (+value).toPrecision(precision);
			}

			value += '';
			var index;
			if (getOption('scientific') === false && value.indexOf('e') > -1) {
				var match = value.split('e'),
						zeros = +match[1];
				value = match[0].replace('.', '');

				if (zeros < 0) {
					zeros = -zeros - 1;
					index = match[0].indexOf('.');
					if (index > -1) {
						zeros -= index - 1;
					}
					while (zeros--) {
						value = '0' + value;
					}
					value = '0.' + value;
				} else {
					index = match[0].lastIndexOf('.');
					if (index > -1) {
						zeros -= match[0].length - index - 1;
					}
					while (zeros--) {
						value += '0';
					}
				}
			}

			if (decimal != '.') {
				value = value.replace('.', decimal);
			}

			if (group) {
				index = value.lastIndexOf(decimal);
				index = (index > -1) ? index : value.length;
				var newOutput = value.substring(index),
				i = index;

				while (i--) {
					if ((index - i - 1) % 3 == 0 && i != (index - 1)) {
						newOutput = group + newOutput;
					}
					newOutput = value.charAt(i) + newOutput;
				}

				value = newOutput;
			}

			if (prefix) { value = prefix + value; }
			if (suffix) { value += suffix; }

			return value;
		},

		formatCurrency: function (decimals) {
			var locale = Locale.get('Number.currency') || {};
			if (locale.scientific == null) { locale.scientific = false; }
			locale.decimals = decimals != null ? decimals : (locale.decimals == null ? 2 : locale.decimals);

			return this.format(locale);
		},

		formatPercentage: function (decimals) {
			var locale = Locale.get('Number.percentage') || {};
			if (locale.suffix == null) { locale.suffix = '%'; }
			locale.decimals = decimals != null ? decimals : (locale.decimals == null ? 2 : locale.decimals);

			return this.format(locale);
		}

	});

	// #endregion Number.Extras

	// #region -- URI Class --

	/**
	* name: URI
	* 
	* description: Provides methods useful in managing the window location and uris.
	* 
	* authors:
	* - Sebastian Markbåge
	* - Aaron Newton
	* 
	* requires:
	* - Core/Object
	* - Core/Class
	* - Core/Class.Extras
	* - Core/Element
	* - /String.QueryString
	* 
	* provides: [URI]
	**/

	(function (document) {

		var toString = function () {
			return this.get('value');
		};

		var URI = this.URI = new Class({

			Implements: Options,

			options: {
				/*base: false*/
			},

			regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
			parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
			schemes: { http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0 },

			initialize: function (uri, options) {
				this.setOptions(options);
				var base = this.options.base || URI.base;
				if (!uri) { uri = base; }

				if (uri && uri.parsed) {
					this.parsed = Object.clone(uri.parsed);
				} else {
					this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
				}
			},

			parse: function (value, base) {
				var bits = value.match(this.regex);
				if (!bits) { return false; }
				bits.shift();
				return this.merge(bits.associate(this.parts), base);
			},

			merge: function (bits, base) {
				if ((!bits || !bits.scheme) && (!base || !base.scheme)) {
					return false;
				}
				if (base) {
					this.parts.every(function (part) {
						if (bits[part]) { return false; }
						bits[part] = base[part] || '';
						return true;
					});
				}
				bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
				bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
				return bits;
			},

			parseDirectory: function (directory, baseDirectory) {
				directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
				if (!directory.test(URI.regs.directoryDot)) {
					return directory;
				}
				var result = [];
				directory.replace(URI.regs.endSlash, '').split('/').each(function (dir) {
					if (dir == '..' && result.length > 0) {
						result.pop();
					} else if (dir != '.') {
						result.push(dir);
					}
				});
				return result.join('/') + '/';
			},

			combine: function (bits) {
				return bits.value || bits.scheme + '://' +
			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
			(bits.directory || '/') + (bits.file || '') +
			(bits.query ? '?' + bits.query : '') +
			(bits.fragment ? '#' + bits.fragment : '');
			},

			set: function (part, value, base) {
				if (part == 'value') {
					var scheme = value.match(URI.regs.scheme);
					if (scheme) { scheme = scheme[1]; }
					if (scheme && this.schemes[scheme.toLowerCase()] == null) {
						this.parsed = { scheme: scheme, value: value };
					} else {
						this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value} : { value: value });
					}
				} else if (part == 'data') {
					this.setData(value);
				} else {
					this.parsed[part] = value;
				}
				return this;
			},

			get: function (part, base) {
				switch (part) {
					case 'value':
						return this.combine(this.parsed, base ? base.parsed : false);

					case 'data':
						return this.getData();
				}
				return this.parsed[part] || '';
			},

			go: function () {
				document.location.href = this.toString();
			},

			toURI: function () {
				return this;
			},

			getData: function (key, part) {
				var qs = this.get(part || 'query');
				if (!(qs || qs === 0)) { return key ? null : {}; }
				var obj = qs.parseQueryString();
				return key ? obj[key] : obj;
			},

			setData: function (values, merge, part) {
				if (typeof values == 'string') {
					var data = this.getData();
					data[arguments[0]] = arguments[1];
					values = data;
				} else if (merge) {
					values = Object.merge(this.getData(), values);
				}
				return this.set(part || 'query', Object.toQueryString(values));
			},

			clearData: function (part) {
				return this.set(part || 'query', '');
			},

			toString: toString,
			valueOf: toString

		});

		URI.regs = {
			endSlash: /\/$/,
			scheme: /^(\w+):/,
			directoryDot: /\.\/|\.$/
		};

		/**
		# 苦苦的苦瓜
		# 2011-09-23
		# 加一条件判断，是否载入了Element
		**/
		if (document.getElements) {
			URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), { base: document.location });
		}

		String.implement({

			toURI: function (options) {
				return new URI(this, options);
			}

		});

	})(document);

	// #endregion URI

	// #region -- URI.Relative --

	/**
	* name: URI.Relative
	* 
	* description: Extends the URI class to add methods for computing relative and absolute urls.
	* 
	* authors:
	*		- Sebastian Markbåge
	* 
	* requires:
	*		- /Class.refactor
	*		- /URI
	* 
	* provides: [URI.Relative]
	**/

	URI = Class.refactor(URI, {

		combine: function (bits, base) {
			if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port) {
				return this.previous.apply(this, arguments);
			}
			var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');

			if (!base.directory) {
				return (bits.directory || (bits.file ? '' : './')) + end;
			}

			var baseDir = base.directory.split('/'),
					relDir = bits.directory.split('/'),
					path = '',
					offset;

			var i = 0, $_len,
					$_lenb = baseDir.length,
					$_lenr = relDir.length; // # 苦苦的苦瓜 2011-10-08
			for (offset = 0; offset < $_lenb && offset < $_lenr && baseDir[offset] == relDir[offset]; offset++) { ; }
			$_len = $_lenb - offset - 1;
			for (i = 0; i < $_len; i++) {
				path += '../';
			}
			$_len = $_lenr - 1;
			for (i = offset; i < $_len; i++) {
				path += relDir[i] + '/';
			}

			return (path || (bits.file ? '' : './')) + end;
		},

		toAbsolute: function (base) {
			base = new URI(base);
			if (base) {
				base.set('directory', '').set('file', '');
			}
			return this.toRelative(base);
		},

		toRelative: function (base) {
			return this.get('value', new URI(base));
		}

	});

	// #endregion URI.Relative

	// #region -- Hash Type --

	/**
	* name: Hash
	* 
	* description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
	* 
	* requires:
	*		- Core/Object
	*		- /MooTools.More
	* 
	* provides: [Hash]
	**/

	(function () {

		if (this.Hash) { return; }

		var Hash = this.Hash = new Type('Hash', function (object) {
			if (typeOf(object) == 'hash') {
				object = Object.clone(object.getClean());
			}
			for (var key in object) {
				this[key] = object[key];
			}
			return this;
		});

		this.$H = function (object) {
			return new Hash(object);
		};

		Hash.implement({

			forEach: function (fn, bind) {
				Object.forEach(this, fn, bind);
			},

			getClean: function () {
				var clean = {};
				for (var key in this) {
					if (this.hasOwnProperty(key)) {
						clean[key] = this[key];
					}
				}
				return clean;
			},

			getLength: function () {
				var length = 0;
				for (var key in this) {
					if (this.hasOwnProperty(key)) { length++; }
				}
				return length;
			}

		});

		Hash.alias('each', 'forEach');

		Hash.implement({

			has: Object.prototype.hasOwnProperty,

			keyOf: function (value) {
				return Object.keyOf(this, value);
			},

			hasValue: function (value) {
				return Object.contains(this, value);
			},

			extend: function (properties) {
				Hash.each(properties || {}, function (value, key) {
					Hash.set(this, key, value);
				}, this);
				return this;
			},

			combine: function (properties) {
				Hash.each(properties || {}, function (value, key) {
					Hash.include(this, key, value);
				}, this);
				return this;
			},

			erase: function (key) {
				if (this.hasOwnProperty(key)) { delete this[key]; }
				return this;
			},

			get: function (key) {
				return (this.hasOwnProperty(key)) ? this[key] : null;
			},

			set: function (key, value) {
				if (!this[key] || this.hasOwnProperty(key)) {
					this[key] = value;
				}
				return this;
			},

			empty: function () {
				Hash.each(this, function (value, key) {
					delete this[key];
				}, this);
				return this;
			},

			include: function (key, value) {
				if (this[key] == undefined) { this[key] = value; }
				return this;
			},

			map: function (fn, bind) {
				return new Hash(Object.map(this, fn, bind));
			},

			filter: function (fn, bind) {
				return new Hash(Object.filter(this, fn, bind));
			},

			every: function (fn, bind) {
				return Object.every(this, fn, bind);
			},

			some: function (fn, bind) {
				return Object.some(this, fn, bind);
			},

			getKeys: function () {
				return Object.keys(this);
			},

			getValues: function () {
				return Object.values(this);
			},

			toQueryString: function (base) {
				return Object.toQueryString(this, base);
			}

		});

		Hash.alias({ indexOf: 'keyOf', contains: 'hasValue' });

	})();

	// #endregion Hash

	// #region -- Hash.Extras --

	/**
	* name: Hash.Extras
	* 
	* description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
	* 
	* authors:
	*		- Aaron Newton
	* 
	* requires:
	*		- /Hash
	*		- /Object.Extras
	* 
	* provides: [Hash.Extras]
	**/

	Hash.implement({

		getFromPath: function (notation) {
			return Object.getFromPath(this, notation);
		},

		cleanValues: function (method) {
			return new Hash(Object.cleanValues(this, method));
		},

		run: function () {
			Object.run(arguments);
		}

	});

	// #endregion Hash.Extras

	// #region -- Color --

	/**
	* name: Color
	* 
	* description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires:
	*		- Core/Array
	*		- Core/String
	*		- Core/Number
	*		- Core/Hash
	*		- Core/Function
	*		- MooTools.More
	* 
	* provides: [Color]
	**/

	(function () {

		var Color = this.Color = new Type('Color', function (color, type) {
			if (arguments.length >= 3) {
				type = 'rgb'; color = Array.slice(arguments, 0, 3);
			} else if (typeof color == 'string') {
				if (color.match(/rgb/)) {
					color = color.rgbToHex().hexToRgb(true);
				} else if (color.match(/hsb/)) {
					color = color.hsbToRgb();
				} else {
					color = color.hexToRgb(true);
				}
			}
			type = type || 'rgb';
			switch (type) {
				case 'hsb':
					var old = color;
					color = color.hsbToRgb();
					color.hsb = old;
					break;

				case 'hex':
					color = color.hexToRgb(true);
					break;
			}
			color.rgb = color.slice(0, 3);
			color.hsb = color.hsb || color.rgbToHsb();
			color.hex = color.rgbToHex();
			return Object.append(color, this);
		});

		Color.implement({

			mix: function () {
				var colors = Array.slice(arguments);
				var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
				var rgb = this.slice();
				colors.each(function (color) {
					color = new Color(color);
					for (var i = 0; i < 3; i++) {
						rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
					}
				});
				return new Color(rgb, 'rgb');
			},

			invert: function () {
				return new Color(this.map(function (value) {
					return 255 - value;
				}));
			},

			setHue: function (value) {
				return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
			},

			setSaturation: function (percent) {
				return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
			},

			setBrightness: function (percent) {
				return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
			}

		});

		this.$RGB = function (r, g, b) {
			return new Color([r, g, b], 'rgb');
		};

		this.$HSB = function (h, s, b) {
			return new Color([h, s, b], 'hsb');
		};

		this.$HEX = function (hex) {
			return new Color(hex, 'hex');
		};

		Array.implement({

			rgbToHsb: function () {
				var red = this[0],
						green = this[1],
						blue = this[2],
						hue = 0;

				var max = Math.max(red, green, blue),
						min = Math.min(red, green, blue),
						$_round = Math.round; // # 苦苦的苦瓜 2011-10-08

				var delta = max - min,
						brightness = max / 255,
						saturation = (max != 0) ? delta / max : 0;
				if (saturation != 0) {
					var rr = (max - red) / delta;
					var gr = (max - green) / delta;
					var br = (max - blue) / delta;
					if (red == max) {
						hue = br - gr;
					} else if (green == max) {
						hue = 2 + rr - br;
					} else {
						hue = 4 + gr - rr;
					}
					hue /= 6;
					if (hue < 0) { hue++; }
				}
				return [$_round(hue * 360), $_round(saturation * 100), $_round(brightness * 100)];
			},

			hsbToRgb: function () {
				var $_round = Math.round, // # 苦苦的苦瓜 2011-10-08
						br = $_round(this[2] / 100 * 255);
				if (this[1] == 0) {
					return [br, br, br];
				} else {
					var hue = this[0] % 360;
					var f = hue % 60;
					var p = $_round((this[2] * (100 - this[1])) / 10000 * 255);
					var q = $_round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
					var t = $_round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
					switch (Math.floor(hue / 60)) {
						case 0:
							return [br, t, p];

						case 1:
							return [q, br, p];

						case 2:
							return [p, br, t];

						case 3:
							return [p, q, br];

						case 4:
							return [t, p, br];

						case 5:
							return [br, p, q];

					}
				}
				return false;
			}

		});

		String.implement({

			rgbToHsb: function () {
				var rgb = this.match(/\d{1,3}/g);
				return (rgb) ? rgb.rgbToHsb() : null;
			},

			hsbToRgb: function () {
				var hsb = this.match(/\d{1,3}/g);
				return (hsb) ? hsb.hsbToRgb() : null;
			}

		});

	})();

	// #endregion

	// #region -- Group --

	/**
	* name: Group
	* 
	* description: Class for monitoring collections of events
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires:
	*		- Core/Events
	*		- /MooTools.More
	* 
	* provides: [Group]
	**/

	// This class is for grouping Classes or Events. The Event added to the Group will fire when all of the events of the items of the group are fired.
	// 把元素分组，然后可以为这些组进行添加事件监听器，这些“组监听器”的触发时机是这样的：当组里面的所有元素都触发过指定的事件后，"组监听器"就开始执行。
	// 比如有3个按钮A,B,C组成一个Group,然后为这个Group添加一个click事件的监听器方法，则，当你分别去按这3个按钮，不分次序，不分按的次数
	// （比如A按了1次，B按了2次,然后再按C）,当按最后一个按钮时候(比如已经按过了A和B，现在按下C)，那个“组监听器”就被执行了。

	(function () {

		// 监听群组事件的类
		this.Group = new Class({

			initialize: function () {
				// 使用Array.flatten降维参数
				this.instances = Array.flatten(arguments);
			},

			// 为群组添加某类事件
			addEvent: function (type, fn) {
				var instances = this.instances,
						len = instances.length,
						togo = len,
						args = new Array(len),
						self = this;

				instances.each(function (instance, i) {
					instance.addEvent(type, function () {
						if (!args[i]) { togo--; }
						args[i] = arguments;
						if (!togo) {
							fn.call(self, instances, instance, args);
							togo = len;
							args = new Array(len);
						}
					});
				});
			}

		});

	})();

	// #endregion

	// #region -- Hash.Cookie --

	/**
	* name: Hash.Cookie
	* 
	* description: Class for creating, reading, and deleting Cookies in JSON format.
	* 
	* authors:
	*		- Valerio Proietti
	*		- Aaron Newton
	* 
	* requires:
	*		- Core/Cookie
	*		- Core/JSON
	*		- /MooTools.More
	*		- /Hash
	* 
	* provides: [Hash.Cookie]
	**/

	Hash.Cookie = new Class({

		Extends: Cookie,

		options: {
			autoSave: true
		},

		initialize: function (name, options) {
			this.parent(name, options);
			this.load();
		},

		save: function () {
			var value = JSON.encode(this.hash);
			if (!value || value.length > 4096) { return false; } //cookie would be truncated!
			if (value == '{}') {
				this.dispose();
			} else {
				this.write(value);
			}
			return true;
		},

		load: function () {
			this.hash = new Hash(JSON.decode(this.read(), true));
			return this;
		}

	});

	Hash.each(Hash.prototype, function (method, name) {
		if (typeof method == 'function') {
			Hash.Cookie.implement(name, function () {
				var value = method.apply(this.hash, arguments);
				if (this.options.autoSave) { this.save(); }
				return value;
			});
		}
	});

	// #endregion

	// #region -- Table --

	/**
	* name: Table
	* 
	* description: LUA-Style table implementation.
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires: [Core/Array]
	* 
	* provides: [Table]
	**/

	(function () {

		var Table = this.Table = function () {

			this.length = 0;
			var keys = [],
					values = [];

			this.set = function (key, value) {
				var index = keys.indexOf(key);
				if (index == -1) {
					var length = keys.length;
					keys[length] = key;
					values[length] = value;
					this.length++;
				} else {
					values[index] = value;
				}
				return this;
			};

			this.get = function (key) {
				var index = keys.indexOf(key);
				return (index == -1) ? null : values[index];
			};

			this.erase = function (key) {
				var index = keys.indexOf(key);
				if (index != -1) {
					this.length--;
					keys.splice(index, 1);
					return values.splice(index, 1)[0];
				}
				return null;
			};

			this.each = this.forEach = function (fn, bind) {
				for (var i = 0, l = this.length; i < l; i++) {
					fn.call(bind, keys[i], values[i], this);
				}
			};

		};

		if (this.Type) { new Type('Table', Table); }

	})();

	// #endregion

});
